package com.winhc.utils;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

/**
 * byte工具类
 * 
 * @author 朱伟
 */
public class ByteUtil {

	/**
	 * 填充类型枚举
	 */
	public enum FillSide {
		left, right;

		/**
		 * 获取填充类型
		 * 
		 * @param name
		 * @return 填充类型
		 */
		public static FillSide get(String name) {
			for (FillSide fs : FillSide.values()) {
				if (fs.name().equals(name)) return fs;
			}
			throw new RuntimeException("非法的FillSide名称：" + name);
		}
	};

	/** 0字节数据 */
	public static final byte[] ZERO_BYTES = new byte[0];

	/**
	 * 依次合并多个个byte数组
	 * 
	 * @param bss byte[][]
	 * @return byte[]
	 */
	public static byte[] union(byte[]... bss) {
		int nx = 0;
		for (byte[] bs : bss)
			nx += bs.length;
		byte[] res = new byte[nx];
		nx = 0;
		for (byte[] bs : bss) {
			System.arraycopy(bs, 0, res, nx, bs.length);
			nx += bs.length;
		}
		return res;
	}

	/**
	 * 截取出byte[]数组的一部分
	 * 
	 * @param bs 截取前的字节数组
	 * @param first 开始位置
	 * @param length 长度
	 * @return byte[] 截取后的字节数组
	 */
	public static byte[] sub(byte[] bs, int first, int length) {
		if ((bs.length - first) < length) throw new RuntimeException("截取长度溢出: bs.length=" + bs.length + ", first=" + first + ", length=" + length);
		length = (first + length) > bs.length ? bs.length - first : length;
		byte[] nb = new byte[length];
		System.arraycopy(bs, first, nb, 0, length);
		return nb;
	}

	/**
	 * 左、右、两边填充字符
	 * 
	 * @param bs 待填充的字符串，可以为null
	 * @param fillByte 填充的字节
	 * @param side 填充的方向，参考XML类中的定义
	 * @param size 输出字符串的固定byte长度。
	 * @return 填充后的byte[]
	 */
	public static byte[] fillByte(byte[] bs, byte fillByte, FillSide side, int size) {
		if (bs.length > size) throw new RuntimeException("需填充的byte数组长度" + bs.length + "大于了填充总长度" + size + "，数据内容[" + toHexInfo(bs, "", " ") + "]");
		if (bs.length == size) return bs;
		byte[] tbs = new byte[size - bs.length];
		Arrays.fill(tbs, fillByte);
		return side == FillSide.left ? union(tbs, bs) : union(bs, tbs);
	}

	/**
	 * 去除(左、右、两边)填充的字节
	 * 
	 * @param bs 待去除字符的字节数组
	 * @param trimByte 待去除的字节
	 * @param trimSide 方向（左、右、两边），参照XML类的定义
	 * @return byte[]
	 */
	public static byte[] trimByte(byte[] bs, byte trimByte, FillSide side) {
		if (side == FillSide.left) {
			int i = 0;
			for (; i < bs.length && bs[i] == trimByte; i++)
				;
			bs = sub(bs, i, bs.length - i);
		}
		if (side.equals(FillSide.right)) {
			int i = bs.length - 1;
			for (; i >= 0 && bs[i] == trimByte; i--)
				;
			bs = sub(bs, 0, i + 1);
		}
		return bs;
	}

	/**
	 * ASCII 转 BCD
	 * <p>
	 * 原理：<br/>
	 * 例如： Ascii: "1234" 转换为 BCD: 0x12 0x34<br/>
	 * 字符0123456789:;<=>?共16个字符的Hex码为[0x3*]其中3表示前4位，*表示后4位，*为0-F，所以在压缩时， 舍弃了字符的前4位，而将两个字符的后4位来组成一个新8位的字符。
	 * </p>
	 * 
	 * @param bs 待转换的ascii字节数组，必须为偶数。
	 * @return 转为bcd后的字节数组。
	 */
	public static byte[] asc2bcd(byte[] bs) {
		byte[] res = new byte[bs.length / 2];
		for (int i = 0, n = bs.length; i < n; i += 2)
			res[i / 2] = (byte) ((bs[i] << 4) | (bs[i + 1] & 0x0f));
		return res;
	}

	/**
	 * BCD 转 ASCII原理
	 * <p>
	 * 例如：0x12 0x34 转换为 "1234"<br/>
	 * 拆分byte的前4位和后4位，然后将这两个4位前面补上0x30(ascii中0123456789:; <=>的共同前4位) 组成两个新的byte.
	 * </p>
	 * 
	 * @param bs 待转换的bcd字节数组
	 * @return 转换为ascii后的字节数组
	 */
	public static byte[] bcd2asc(byte[] bs) {
		byte[] res = new byte[bs.length * 2];
		for (int i = 0, n = bs.length; i < n; i++) {
			res[i * 2] = (byte) (((bs[i] & 0xf0) >> 4) | 0x30);
			res[i * 2 + 1] = (byte) ((bs[i] & 0x0f) | 0x30);
		}
		return res;
	}

	/**
	 * 将16进制方式转换为byte
	 * <p>
	 * 例如："AB" 转换为0xAB对应的byte
	 * </p>
	 * 
	 * @param bs 待转换的byte数组
	 * @return 压缩为16进制后的byte数组
	 */
	public static byte[] hex2byte(byte[] bs) {
		if (bs.length % 2 == 1) // 如果为奇数，则左补0
		bs = union("0".getBytes(), bs);
		byte[] res = new byte[bs.length / 2];
		for (int i = 0, n = bs.length; i < n; i += 2)
			res[i / 2] = (byte) (Integer.parseInt(new String(bs, i, 2), 16));
		return res;
	}

	/**
	 * 将16进制的字符串转换为byte[].
	 * <p>
	 * 例如：ABCDEF转化为0xAB, 0xCD, 0xEF 的数组
	 * </p>
	 * 
	 * @param str 字符串
	 * @return byte[] 字节数组
	 */
	public static byte[] hex2byte(String str) {
		return hex2byte(str.getBytes());
	}

	/**
	 * 将byte转换为16进制方式
	 * <p>
	 * 例如：字节0xAB转换为Ascii的两个字符AB
	 * </p>
	 * 
	 * @param bs 待转换的16进制的数组
	 * @return 解压后的Ascii数组
	 */
	public static byte[] byte2hex(byte[] bs) {
		byte[] res = new byte[bs.length * 2];
		for (int i = 0; i < bs.length; i++) {
			// 需要将大于127的byte转换为正的int型
			int ti = bs[i];
			ti = ti < 0 ? ti + 256 : ti;
			String t = Integer.toHexString(ti);
			if (t.length() < 2) t = "0" + t;
			res[i * 2] = (byte) t.charAt(0);
			res[i * 2 + 1] = (byte) t.charAt(1);
		}
		return res;
	}

	/**
	 * 组装BitMap
	 * <p>
	 * 将代表位图的boolean数组转化为长度length/8后的byte数组
	 * </p>
	 * 
	 * @param source boolean数组
	 * @return byte[] 字节数组
	 */
	public static byte[] makeBitMap(boolean[] source) {
		int n = source.length / 8;
		byte[] bs = new byte[n];
		for (int i = 0; i < n; i++) {
			char c = 0x00;
			for (int j = 0; j < 8; j++)
				c = source[8 * i + j] ? (char) (c | (1 << (7 - j))) : c;
			bs[i] = (byte) c;
		}
		return bs;
	}

	/**
	 * 拆分BitMap
	 * <p>
	 * 将字节数组转化为长度length*8之后的boolean位图数组
	 * </p>
	 * 
	 * @param bs 字节数组
	 * @return boolean[]
	 */
	public static boolean[] splitBitMap(byte[] bs) {
		boolean[] res = new boolean[bs.length * 8];
		for (int i = 0; i < bs.length; i++) {
			int x = bs[i] < 0 ? (bs[i] + 256) : bs[i];
			for (int j = 0; j < 8; j++)
				res[i * 8 + j] = (x & 1 << (7 - j)) == (1 << (7 - j)) ? true : false;
		}
		return res;
	}

	/**
	 * byte[] 转 int
	 * 
	 * @param bs 字节数组
	 * @return 整数
	 */
	public static int bytes2int(byte[] bs) {
		int mask = 0xff;
		int temp = 0;
		int res = 0;
		for (int i = 0; i < bs.length; i++) {
			res <<= 8;
			temp = bs[i] & mask;
			res |= temp;
		}
		return res;
	}

	/**
	 * int 转 byte[]
	 * 
	 * @param num 数值
	 * @return byte[] 字节数组
	 */
	public static byte[] int2bytes(int num) {
		if (num == 0) return new byte[] { 0x00 };
		byte[] tbs = new byte[4];
		int p = 0;
		for (int i = 0; i < 4; i++) {
			tbs[i] = (byte) (num >>> (24 - i * 8));
			if (p == 0 && tbs[i] != 0) p = i;
		}
		return sub(tbs, p, tbs.length - p);
	}

	/**
	 * 将byte[]格式化为aa 1f f2格式
	 * 
	 * @param bs 字节数组
	 * @param prefix 每个字节的前缀,可以为null,为null则是空字符串
	 * @param splitStr 字节之间的分割符，可以为null,为null则是空字符串
	 * @return String 格式化后的字符串
	 */
	public static String toHexInfo(byte[] bs, String prefix, String splitStr) {
		prefix = prefix == null ? "" : prefix;
		splitStr = splitStr == null ? "" : splitStr;
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < bs.length; i++) {
			int nn = bs[i] < 0 ? bs[i] + 256 : bs[i];
			String t = Integer.toHexString(nn).toUpperCase();
			sb.append(prefix).append(t.length() < 2 ? "0" + t : t).append(i < bs.length - 1 ? splitStr : "");
		}
		return sb.toString();
	}

	/**
	 * 简单的16进制输出
	 * 
	 * @param bs 字节数组
	 * @return 16进制
	 */
	public static String toHexInfo(byte[] bs) {
		return toHexInfo(bs, "", "");
	}

	/**
	 * 读取输入流中的所有数据，不会关闭输入流
	 * 
	 * @param is 输入流
	 * @return byte[] 入流中的所有数据
	 * @throws IOException
	 */
	public static byte[] read(InputStream is) throws IOException {
		return read(is, 0);
	}

	/**
	 * 读取输入流中的数据，不会关闭输入流
	 * 
	 * @param is 输入流
	 * @param maxLength 最大长度，0表示全部
	 * @return 输入流中的数据
	 * @throws IOException
	 */
	public static byte[] read(InputStream is, int maxLength) throws IOException {
		// 指定长度
		if (maxLength > 0) {
			byte[] bs = new byte[maxLength];
			int n = is.read(bs);
			return sub(bs, 0, n);
		}
		// 全部
		else {
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			byte[] bs = new byte[1024];
			BufferedInputStream bis = new BufferedInputStream(is, 1024);
			int n = 0;
			while ((n = bis.read(bs)) > 0)
				bos.write(bs, 0, n);
			return bos.toByteArray();
		}
	}

	public static void main(String[] str) {
		// byte[]bs=ByteUtil.union("ABC".getBytes(),"DEF".getBytes(),"123".getBytes());
		byte[] bs = int2bytes(0);
		System.out.println(toHexInfo(bs, "0x", ", "));
		System.out.println(bytes2int(bs));
	}

}
